1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.runtime;
20  
21  import groovy.lang.*;
22  
23  import org.codehaus.groovy.reflection.CachedClass;
24  import org.codehaus.groovy.util.FastArray;
25  import org.codehaus.groovy.reflection.ParameterTypes;
26  import org.codehaus.groovy.reflection.ReflectionCache;
27  import org.codehaus.groovy.runtime.wrappers.Wrapper;
28  
29  import java.lang.reflect.Array;
30  import java.lang.reflect.Constructor;
31  import java.lang.reflect.Modifier;
32  import java.math.BigDecimal;
33  import java.math.BigInteger;
34  import java.util.Arrays;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Set;
38  import java.util.logging.Level;
39  import java.util.logging.Logger;
40  
41  /**
42   * @author John Wilson
43   * @author Jochen Theodorou
44   */
45  public class MetaClassHelper {
46  
47      public static final Object[] EMPTY_ARRAY = {};
48      public static final Class[] EMPTY_TYPE_ARRAY = {};
49      public static final Object[] ARRAY_WITH_NULL = {null};
50      protected static final Logger LOG = Logger.getLogger(MetaClassHelper.class.getName());
51      private static final int MAX_ARG_LEN = 12;
52      private static final int
53              OBJECT_SHIFT = 23, INTERFACE_SHIFT = 0,
54              PRIMITIVE_SHIFT = 21, VARGS_SHIFT = 44;
55      /* dist binary layout:
56      * 0-20: interface
57      * 21-22: primitive dist
58      * 23-43: object dist
59      * 44-48: vargs penalty
60      */
61  
62      public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
63  
64      public static boolean accessibleToConstructor(final Class at, final Constructor constructor) {
65          boolean accessible = false;
66          final int modifiers = constructor.getModifiers();
67          if (Modifier.isPublic(modifiers)) {
68              accessible = true;
69          } else if (Modifier.isPrivate(modifiers)) {
70              accessible = at.getName().equals(constructor.getName());
71          } else if (Modifier.isProtected(modifiers)) {
72              Boolean isAccessible = checkCompatiblePackages(at, constructor);
73              if (isAccessible != null) {
74                  accessible = isAccessible;
75              } else {
76                  boolean flag = false;
77                  Class clazz = at;
78                  while (!flag && clazz != null) {
79                      if (clazz.equals(constructor.getDeclaringClass())) {
80                          flag = true;
81                          break;
82                      }
83                      if (clazz.equals(Object.class)) {
84                          break;
85                      }
86                      clazz = clazz.getSuperclass();
87                  }
88                  accessible = flag;
89              }
90          } else {
91              Boolean isAccessible = checkCompatiblePackages(at, constructor);
92              if (isAccessible != null) {
93                  accessible = isAccessible;
94              }
95          }
96          return accessible;
97      }
98  
99      private static Boolean checkCompatiblePackages(Class at, Constructor constructor) {
100         if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null) {
101             return Boolean.TRUE;
102         }
103         if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null) {
104             return Boolean.FALSE;
105         }
106         if (at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null) {
107             return Boolean.FALSE;
108         }
109         if (at.getPackage().equals(constructor.getDeclaringClass().getPackage())) {
110             return Boolean.TRUE;
111         }
112         return null;
113     }
114 
115     public static Object[] asWrapperArray(Object parameters, Class componentType) {
116         Object[] ret = null;
117         if (componentType == boolean.class) {
118             boolean[] array = (boolean[]) parameters;
119             ret = new Object[array.length];
120             for (int i = 0; i < array.length; i++) {
121                 ret[i] = array[i];
122             }
123         } else if (componentType == char.class) {
124             char[] array = (char[]) parameters;
125             ret = new Object[array.length];
126             for (int i = 0; i < array.length; i++) {
127                 ret[i] = array[i];
128             }
129         } else if (componentType == byte.class) {
130             byte[] array = (byte[]) parameters;
131             ret = new Object[array.length];
132             for (int i = 0; i < array.length; i++) {
133                 ret[i] = array[i];
134             }
135         } else if (componentType == int.class) {
136             int[] array = (int[]) parameters;
137             ret = new Object[array.length];
138             for (int i = 0; i < array.length; i++) {
139                 ret[i] = array[i];
140             }
141         } else if (componentType == short.class) {
142             short[] array = (short[]) parameters;
143             ret = new Object[array.length];
144             for (int i = 0; i < array.length; i++) {
145                 ret[i] = array[i];
146             }
147         } else if (componentType == long.class) {
148             long[] array = (long[]) parameters;
149             ret = new Object[array.length];
150             for (int i = 0; i < array.length; i++) {
151                 ret[i] = array[i];
152             }
153         } else if (componentType == double.class) {
154             double[] array = (double[]) parameters;
155             ret = new Object[array.length];
156             for (int i = 0; i < array.length; i++) {
157                 ret[i] = array[i];
158             }
159         } else if (componentType == float.class) {
160             float[] array = (float[]) parameters;
161             ret = new Object[array.length];
162             for (int i = 0; i < array.length; i++) {
163                 ret[i] = array[i];
164             }
165         }
166 
167         return ret;
168     }
169 
170 
171     /**
172      * @param list          the original list
173      * @param parameterType the resulting array type
174      * @return the constructed array
175      */
176     public static Object asPrimitiveArray(List list, Class parameterType) {
177         Class arrayType = parameterType.getComponentType();
178         Object objArray = Array.newInstance(arrayType, list.size());
179         for (int i = 0; i < list.size(); i++) {
180             Object obj = list.get(i);
181             if (arrayType.isPrimitive()) {
182                 if (obj instanceof Integer) {
183                     Array.setInt(objArray, i, (Integer) obj);
184                 } else if (obj instanceof Double) {
185                     Array.setDouble(objArray, i, (Double) obj);
186                 } else if (obj instanceof Boolean) {
187                     Array.setBoolean(objArray, i, (Boolean) obj);
188                 } else if (obj instanceof Long) {
189                     Array.setLong(objArray, i, (Long) obj);
190                 } else if (obj instanceof Float) {
191                     Array.setFloat(objArray, i, (Float) obj);
192                 } else if (obj instanceof Character) {
193                     Array.setChar(objArray, i, (Character) obj);
194                 } else if (obj instanceof Byte) {
195                     Array.setByte(objArray, i, (Byte) obj);
196                 } else if (obj instanceof Short) {
197                     Array.setShort(objArray, i, (Short) obj);
198                 }
199             } else {
200                 Array.set(objArray, i, obj);
201             }
202         }
203         return objArray;
204     }
205 
206     private static final Class[] PRIMITIVES = {
207             byte.class, Byte.class, short.class, Short.class,
208             int.class, Integer.class, long.class, Long.class,
209             BigInteger.class, float.class, Float.class,
210             double.class, Double.class, BigDecimal.class,
211             Number.class, Object.class
212     };
213     private static final int[][] PRIMITIVE_DISTANCE_TABLE = {
214             //              byte    Byte    short   Short   int     Integer     long    Long    BigInteger  float   Float   double  Double  BigDecimal, Number, Object
215             /* byte*/{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,},
216             /*Byte*/{1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,},
217             /*short*/{14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,},
218             /*Short*/{14, 15, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,},
219             /*int*/{14, 15, 12, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,},
220             /*Integer*/{14, 15, 12, 13, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,},
221             /*long*/{14, 15, 12, 13, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,},
222             /*Long*/{14, 15, 12, 13, 10, 11, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9,},
223             /*BigInteger*/{9, 10, 7, 8, 5, 6, 3, 4, 0, 14, 15, 12, 13, 11, 1, 2,},
224             /*float*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 0, 1, 2, 3, 4, 5, 6,},
225             /*Float*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 1, 0, 2, 3, 4, 5, 6,},
226             /*double*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 0, 1, 2, 3, 4,},
227             /*Double*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 1, 0, 2, 3, 4,},
228             /*BigDecimal*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 0, 1, 2,},
229             /*Number*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2, 0, 1,},
230             /*Object*/{14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2, 1, 0,},
231     };
232 
233     private static int getPrimitiveIndex(Class c) {
234         for (byte i = 0; i < PRIMITIVES.length; i++) {
235             if (PRIMITIVES[i] == c) return i;
236         }
237         return -1;
238     }
239 
240     private static int getPrimitiveDistance(Class from, Class to) {
241         // we know here that from!=to, so a distance of 0 is never valid
242         // get primitive type indexes
243         int fromIndex = getPrimitiveIndex(from);
244         int toIndex = getPrimitiveIndex(to);
245         if (fromIndex == -1 || toIndex == -1) return -1;
246         return PRIMITIVE_DISTANCE_TABLE[toIndex][fromIndex];
247     }
248 
249     private static int getMaximumInterfaceDistance(Class c, Class interfaceClass) {
250         // -1 means a mismatch
251         if (c == null) return -1;
252         // 0 means a direct match
253         if (c == interfaceClass) return 0;
254         Class[] interfaces = c.getInterfaces();
255         int max = -1;
256         for (Class anInterface : interfaces) {
257             int sub = getMaximumInterfaceDistance(anInterface, interfaceClass);
258             // we need to keep the -1 to track the mismatch, a +1
259             // by any means could let it look like a direct match
260             // we want to add one, because there is an interface between
261             // the interface we search for and the interface we are in.
262             if (sub != -1) sub++;
263             // we are interested in the longest path only
264             max = Math.max(max, sub);
265         }
266         // we do not add one for super classes, only for interfaces
267         int superClassMax = getMaximumInterfaceDistance(c.getSuperclass(), interfaceClass);
268         if (superClassMax != -1) superClassMax++;
269         return Math.max(max, superClassMax);
270     }
271 
272     private static long calculateParameterDistance(Class argument, CachedClass parameter) {
273         /**
274          * note: when shifting with 32 bit, you should only shift on a long. If you do
275          *       that with an int, then i==(i<<32), which means you loose the shift
276          *       information
277          */
278 
279         if (parameter.getTheClass() == argument) return 0;
280 
281         if (parameter.isInterface()) {
282             int dist = getMaximumInterfaceDistance(argument, parameter.getTheClass()) << INTERFACE_SHIFT;
283             if (dist>-1 || !(argument!=null && Closure.class.isAssignableFrom(argument))) {
284                 return dist;
285             } // else go to object case
286         }
287 
288         long objectDistance = 0;
289         if (argument != null) {
290             long pd = getPrimitiveDistance(parameter.getTheClass(), argument);
291             if (pd != -1) return pd << PRIMITIVE_SHIFT;
292 
293             // add one to dist to be sure interfaces are preferred
294             objectDistance += PRIMITIVES.length + 1;
295 
296             // GROOVY-5114 : if we have to choose between two methods
297             // foo(Object[]) and foo(Object) and that the argument is an array type
298             // then the array version should be preferred
299             if (argument.isArray() && !parameter.isArray) {
300                 objectDistance+=4;
301             }
302             Class clazz = ReflectionCache.autoboxType(argument);
303             while (clazz != null) {
304                 if (clazz == parameter.getTheClass()) break;
305                 if (clazz == GString.class && parameter.getTheClass() == String.class) {
306                     objectDistance += 2;
307                     break;
308                 }
309                 clazz = clazz.getSuperclass();
310                 objectDistance += 3;
311             }
312         } else {
313             // choose the distance to Object if a parameter is null
314             // this will mean that Object is preferred over a more
315             // specific type
316             Class clazz = parameter.getTheClass();
317             if (clazz.isPrimitive()) {
318                 objectDistance += 2;
319             } else {
320                 while (clazz != Object.class && clazz != null) {
321                     clazz = clazz.getSuperclass();
322                     objectDistance += 2;
323                 }
324             }
325         }
326         return objectDistance << OBJECT_SHIFT;
327     }
328 
329     public static long calculateParameterDistance(Class[] arguments, ParameterTypes pt) {
330         CachedClass[] parameters = pt.getParameterTypes();
331         if (parameters.length == 0) return 0;
332 
333         long ret = 0;
334         int noVargsLength = parameters.length - 1;
335 
336         // if the number of parameters does not match we have 
337         // a vargs usage
338         //
339         // case A: arguments.length<parameters.length
340         //
341         //         In this case arguments.length is always equal to
342         //         noVargsLength because only the last parameter
343         //         might be a optional vargs parameter
344         //
345         //         VArgs penalty: 1l
346         //
347         // case B: arguments.length>parameters.length
348         //
349         //         In this case all arguments with a index bigger than
350         //         paramMinus1 are part of the vargs, so a 
351         //         distance calculation needs to be done against 
352         //         parameters[noVargsLength].getComponentType()
353         //
354         //         VArgs penalty: 2l+arguments.length-parameters.length
355         //
356         // case C: arguments.length==parameters.length && 
357         //         isAssignableFrom( parameters[noVargsLength],
358         //                           arguments[noVargsLength] )
359         //
360         //         In this case we have no vargs, so calculate directly
361         //
362         //         VArgs penalty: 0l
363         //
364         // case D: arguments.length==parameters.length && 
365         //         !isAssignableFrom( parameters[noVargsLength],
366         //                            arguments[noVargsLength] )
367         //
368         //         In this case we have a vargs case again, we need 
369         //         to calculate arguments[noVargsLength] against
370         //         parameters[noVargsLength].getComponentType
371         //
372         //         VArgs penalty: 2l
373         //
374         //         This gives: VArgs_penalty(C)<VArgs_penalty(A)
375         //                     VArgs_penalty(A)<VArgs_penalty(D)
376         //                     VArgs_penalty(D)<VArgs_penalty(B)
377 
378         /**
379          * In general we want to match the signature that allows us to use
380          * as less arguments for the vargs part as possible. That means the
381          * longer signature usually wins if both signatures are vargs, while
382          * vargs looses always against a signature without vargs.
383          *
384          *  A vs B :
385          *      def foo(Object[] a) {1}     -> case B
386          *      def foo(a,b,Object[] c) {2} -> case A
387          *      assert foo(new Object(),new Object()) == 2
388          *  --> A preferred over B
389          *
390          *  A vs C :
391          *      def foo(Object[] a) {1}     -> case B
392          *      def foo(a,b)        {2}     -> case C
393          *      assert foo(new Object(),new Object()) == 2
394          *  --> C preferred over A
395          *
396          *  A vs D :
397          *      def foo(Object[] a) {1}     -> case D
398          *      def foo(a,Object[] b) {2}   -> case A
399          *      assert foo(new Object()) == 2
400          *  --> A preferred over D
401          *
402          *  This gives C<A<B,D
403          *
404          *  B vs C :
405          *      def foo(Object[] a) {1}     -> case B
406          *      def foo(a,b) {2}            -> case C
407          *      assert foo(new Object(),new Object()) == 2
408          *  --> C preferred over B, matches C<A<B,D
409          *
410          *  B vs D :
411          *      def foo(Object[] a)   {1}   -> case B
412          *      def foo(a,Object[] b) {2}   -> case D
413          *      assert foo(new Object(),new Object()) == 2
414          *  --> D preferred over B
415          *
416          *  This gives C<A<D<B 
417          */
418 
419         // first we calculate all arguments, that are for sure not part
420         // of vargs.  Since the minimum for arguments is noVargsLength
421         // we can safely iterate to this point
422         for (int i = 0; i < noVargsLength; i++) {
423             ret += calculateParameterDistance(arguments[i], parameters[i]);
424         }
425 
426         if (arguments.length == parameters.length) {
427             // case C&D, we use baseType to calculate and set it
428             // to the value we need according to case C and D
429             CachedClass baseType = parameters[noVargsLength]; // case C
430             if (!parameters[noVargsLength].isAssignableFrom(arguments[noVargsLength])) {
431                 baseType = ReflectionCache.getCachedClass(baseType.getTheClass().getComponentType()); // case D
432                 ret += 2l << VARGS_SHIFT; // penalty for vargs
433             }
434             ret += calculateParameterDistance(arguments[noVargsLength], baseType);
435         } else if (arguments.length > parameters.length) {
436             // case B
437             // we give our a vargs penalty for each exceeding argument and iterate
438             // by using parameters[noVargsLength].getComponentType()
439             ret += (2l + arguments.length - parameters.length) << VARGS_SHIFT; // penalty for vargs
440             CachedClass vargsType = ReflectionCache.getCachedClass(parameters[noVargsLength].getTheClass().getComponentType());
441             for (int i = noVargsLength; i < arguments.length; i++) {
442                 ret += calculateParameterDistance(arguments[i], vargsType);
443             }
444         } else {
445             // case A
446             // we give a penalty for vargs, since we have no direct
447             // match for the last argument
448             ret += 1l << VARGS_SHIFT;
449         }
450 
451         return ret;
452     }
453 
454     /**
455      * This is the complement to the java.beans.Introspector.decapitalize(String) method.
456      * We handle names that begin with an initial lowerCase followed by upperCase specially
457      * (which is to make no change).
458      * See GROOVY-3211.
459      *
460      * @param property the property name to capitalize
461      * @return the name capitalized, except when we don't
462      */
463     public static String capitalize(final String property) {
464         final String rest = property.substring(1);
465 
466         // Funky rule so that names like 'pNAME' will still work.
467         if (Character.isLowerCase(property.charAt(0)) && (rest.length() > 0) && Character.isUpperCase(rest.charAt(0))) {
468             return property;
469         }
470 
471         return property.substring(0, 1).toUpperCase() + rest;
472     }
473 
474     /**
475      * @param methods the methods to choose from
476      * @return the method with 1 parameter which takes the most general type of
477      *         object (e.g. Object)
478      */
479     public static Object chooseEmptyMethodParams(FastArray methods) {
480         Object vargsMethod = null;
481         final int len = methods.size();
482         final Object[] data = methods.getArray();
483         for (int i = 0; i != len; ++i) {
484             Object method = data[i];
485             final ParameterTypes pt = (ParameterTypes) method;
486             CachedClass[] paramTypes = pt.getParameterTypes();
487             int paramLength = paramTypes.length;
488             if (paramLength == 0) {
489                 return method;
490             } else if (paramLength == 1 && pt.isVargsMethod(EMPTY_ARRAY)) {
491                 vargsMethod = method;
492             }
493         }
494         return vargsMethod;
495     }
496 
497     /**
498      * Warning: this method does not choose properly if multiple methods with
499      * the same distance are encountered
500      * @param methods the methods to choose from
501      * @return the method with 1 parameter which takes the most general type of
502      *         object (e.g. Object) ignoring primitive types
503      * @deprecated
504      */
505     public static Object chooseMostGeneralMethodWith1NullParam(FastArray methods) {
506         // let's look for methods with 1 argument which matches the type of the
507         // arguments
508         CachedClass closestClass = null;
509         CachedClass closestVargsClass = null;
510         Object answer = null;
511         int closestDist = -1;
512         final int len = methods.size();
513         for (int i = 0; i != len; ++i) {
514             final Object[] data = methods.getArray();
515             Object method = data[i];
516             final ParameterTypes pt = (ParameterTypes) method;
517             CachedClass[] paramTypes = pt.getParameterTypes();
518             int paramLength = paramTypes.length;
519             if (paramLength == 0 || paramLength > 2) continue;
520 
521             CachedClass theType = paramTypes[0];
522             if (theType.isPrimitive) continue;
523 
524             if (paramLength == 2) {
525                 if (!pt.isVargsMethod(ARRAY_WITH_NULL)) continue;
526                 if (closestClass == null) {
527                     closestVargsClass = paramTypes[1];
528                     closestClass = theType;
529                     answer = method;
530                 } else if (closestClass.getTheClass() == theType.getTheClass()) {
531                     if (closestVargsClass == null) continue;
532                     CachedClass newVargsClass = paramTypes[1];
533                     if (isAssignableFrom(newVargsClass.getTheClass(), closestVargsClass.getTheClass())) {
534                         closestVargsClass = newVargsClass;
535                         answer = method;
536                     }
537                 } else if (isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) {
538                     closestVargsClass = paramTypes[1];
539                     closestClass = theType;
540                     answer = method;
541                 }
542             } else {
543                 if (closestClass == null || isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) {
544                     closestVargsClass = null;
545                     closestClass = theType;
546                     answer = method;
547                     closestDist = -1;
548                 } else {
549                     // closestClass and theType are not in a subtype relation, we need
550                     // to check the distance to Object
551                     if (closestDist == -1) closestDist = closestClass.getSuperClassDistance();
552                     int newDist = theType.getSuperClassDistance();
553                     if (newDist < closestDist) {
554                         closestDist = newDist;
555                         closestVargsClass = null;
556                         closestClass = theType;
557                         answer = method;
558                     }
559                 }
560             }
561         }
562         return answer;
563     }
564 
565     // 
566 
567     private static int calculateSimplifiedClassDistanceToObject(Class clazz) {
568         int objectDistance = 0;
569         while (clazz != null) {
570             clazz = clazz.getSuperclass();
571             objectDistance++;
572         }
573         return objectDistance;
574     }
575 
576 
577     /**
578      * @param list   a list of MetaMethods
579      * @param method the MetaMethod of interest
580      * @return true if a method of the same matching prototype was found in the
581      *         list
582      */
583     public static boolean containsMatchingMethod(List list, MetaMethod method) {
584         for (Object aList : list) {
585             MetaMethod aMethod = (MetaMethod) aList;
586             CachedClass[] params1 = aMethod.getParameterTypes();
587             CachedClass[] params2 = method.getParameterTypes();
588             if (params1.length == params2.length) {
589                 boolean matches = true;
590                 for (int i = 0; i < params1.length; i++) {
591                     if (params1[i] != params2[i]) {
592                         matches = false;
593                         break;
594                     }
595                 }
596                 if (matches) {
597                     return true;
598                 }
599             }
600         }
601         return false;
602     }
603 
604     /**
605      * param instance array to the type array
606      *
607      * @param args the arguments
608      * @return the types of the arguments
609      */
610     public static Class[] convertToTypeArray(Object[] args) {
611         if (args == null)
612             return null;
613         int s = args.length;
614         Class[] ans = new Class[s];
615         for (int i = 0; i < s; i++) {
616             Object o = args[i];
617             ans[i] = getClassWithNullAndWrapper(o);
618         }
619         return ans;
620     }
621 
622     public static Object makeCommonArray(Object[] arguments, int offset, Class fallback) {
623         // arguments.length>0 && !=null
624         Class baseClass = null;
625         for (int i = offset; i < arguments.length; i++) {
626             if (arguments[i] == null) continue;
627             Class argClass = arguments[i].getClass();
628             if (baseClass == null) {
629                 baseClass = argClass;
630             } else {
631                 for (; baseClass != Object.class; baseClass = baseClass.getSuperclass()) {
632                     if (baseClass.isAssignableFrom(argClass)) break;
633                 }
634             }
635         }
636         if (baseClass == null) {
637             // all arguments were null
638             baseClass = fallback;
639         }
640         /*
641          * If no specific super class has been found and type fallback is an interface, check if all arg classes 
642          * implement it. If yes, then that interface is the common type across arguments.
643          */
644         if (baseClass == Object.class && fallback.isInterface()) {
645             int tmpCount = 0;
646             for (int i = offset; i < arguments.length; i++) {
647                 if (arguments[i] != null) {
648                     Class argClass, tmpClass;
649                     Set<Class> intfs = new HashSet<Class>();
650                     tmpClass = argClass = arguments[i].getClass();
651                     for (; tmpClass != Object.class; tmpClass = tmpClass.getSuperclass()) {
652                         intfs.addAll(Arrays.asList(tmpClass.getInterfaces()));
653                     }
654                     if (intfs.contains(fallback)) {
655                         tmpCount++;
656                     }
657                 }
658             }
659             // all arg classes implement interface fallback, so use that as the array component type
660             if (tmpCount == arguments.length - offset) {
661                 baseClass = fallback;
662             }
663         }
664         Object result = makeArray(null, baseClass, arguments.length - offset);
665         System.arraycopy(arguments, offset, result, 0, arguments.length - offset);
666         return result;
667     }
668 
669     public static Object makeArray(Object obj, Class secondary, int length) {
670         Class baseClass = secondary;
671         if (obj != null) {
672             baseClass = obj.getClass();
673         }
674         /*if (GString.class.isAssignableFrom(baseClass)) {
675               baseClass = GString.class;
676           }*/
677         return Array.newInstance(baseClass, length);
678     }
679 
680     public static GroovyRuntimeException createExceptionText(String init, MetaMethod method, Object object, Object[] args, Throwable reason, boolean setReason) {
681         return new GroovyRuntimeException(
682                 init
683                         + method
684                         + " on: "
685                         + object
686                         + " with arguments: "
687                         + InvokerHelper.toString(args)
688                         + " reason: "
689                         + reason,
690                 setReason ? reason : null);
691     }
692 
693     protected static String getClassName(Object object) {
694         if (object == null) return null;
695         return (object instanceof Class) ? ((Class) object).getName() : object.getClass().getName();
696     }
697 
698     /**
699      * Returns a callable object for the given method name on the object.
700      * The object acts like a Closure in that it can be called, like a closure
701      * and passed around - though really its a method pointer, not a closure per se.
702      *
703      * @param object     the object containing the method
704      * @param methodName the method of interest
705      * @return the resulting closure-like method pointer
706      */
707     public static Closure getMethodPointer(Object object, String methodName) {
708         return new MethodClosure(object, methodName);
709     }
710 
711     public static boolean isAssignableFrom(Class classToTransformTo, Class classToTransformFrom) {
712         if (classToTransformTo == classToTransformFrom
713                 || classToTransformFrom == null
714                 || classToTransformTo == Object.class) {
715             return true;
716         }
717 
718         classToTransformTo = ReflectionCache.autoboxType(classToTransformTo);
719         classToTransformFrom = ReflectionCache.autoboxType(classToTransformFrom);
720         if (classToTransformTo == classToTransformFrom) return true;
721 
722         // note: there is no coercion for boolean and char. Range matters, precision doesn't
723         if (classToTransformTo == Integer.class) {
724             if (classToTransformFrom == Short.class
725                     || classToTransformFrom == Byte.class
726                     || classToTransformFrom == BigInteger.class)
727                 return true;
728         } else if (classToTransformTo == Double.class) {
729             if (classToTransformFrom == Integer.class
730                     || classToTransformFrom == Long.class
731                     || classToTransformFrom == Short.class
732                     || classToTransformFrom == Byte.class
733                     || classToTransformFrom == Float.class
734                     || classToTransformFrom == BigDecimal.class
735                     || classToTransformFrom == BigInteger.class)
736                 return true;
737         } else if (classToTransformTo == BigDecimal.class) {
738             if (classToTransformFrom == Double.class
739                     || classToTransformFrom == Integer.class
740                     || classToTransformFrom == Long.class
741                     || classToTransformFrom == Short.class
742                     || classToTransformFrom == Byte.class
743                     || classToTransformFrom == Float.class
744                     || classToTransformFrom == BigInteger.class)
745                 return true;
746         } else if (classToTransformTo == BigInteger.class) {
747             if (classToTransformFrom == Integer.class
748                     || classToTransformFrom == Long.class
749                     || classToTransformFrom == Short.class
750                     || classToTransformFrom == Byte.class)
751                 return true;
752         } else if (classToTransformTo == Long.class) {
753             if (classToTransformFrom == Integer.class
754                     || classToTransformFrom == Short.class
755                     || classToTransformFrom == Byte.class)
756                 return true;
757         } else if (classToTransformTo == Float.class) {
758             if (classToTransformFrom == Integer.class
759                     || classToTransformFrom == Long.class
760                     || classToTransformFrom == Short.class
761                     || classToTransformFrom == Byte.class)
762                 return true;
763         } else if (classToTransformTo == Short.class) {
764             if (classToTransformFrom == Byte.class)
765                 return true;
766         } else if (classToTransformTo == String.class) {
767             if (GString.class.isAssignableFrom(classToTransformFrom)) {
768                 return true;
769             }
770         }
771 
772         return ReflectionCache.isAssignableFrom(classToTransformTo, classToTransformFrom);
773     }
774 
775     public static boolean isGenericSetMethod(MetaMethod method) {
776         return (method.getName().equals("set"))
777                 && method.getParameterTypes().length == 2;
778     }
779 
780     protected static boolean isSuperclass(Class clazz, Class superclass) {
781         while (clazz != null) {
782             if (clazz == superclass) return true;
783             clazz = clazz.getSuperclass();
784         }
785         return false;
786     }
787 
788     public static boolean parametersAreCompatible(Class[] arguments, Class[] parameters) {
789         if (arguments.length != parameters.length) return false;
790         for (int i = 0; i < arguments.length; i++) {
791             if (!isAssignableFrom(parameters[i], arguments[i])) return false;
792         }
793         return true;
794     }
795 
796     public static void logMethodCall(Object object, String methodName, Object[] arguments) {
797         String className = getClassName(object);
798         String logname = "methodCalls." + className + "." + methodName;
799         Logger objLog = Logger.getLogger(logname);
800         if (!objLog.isLoggable(Level.FINER)) return;
801         StringBuilder msg = new StringBuilder(methodName);
802         msg.append("(");
803         if (arguments != null) {
804             for (int i = 0; i < arguments.length;) {
805                 msg.append(normalizedValue(arguments[i]));
806                 if (++i < arguments.length) {
807                     msg.append(",");
808                 }
809             }
810         }
811         msg.append(")");
812         objLog.logp(Level.FINER, className, msg.toString(), "called from MetaClass.invokeMethod");
813     }
814 
815     protected static String normalizedValue(Object argument) {
816         String value;
817         try {
818             value = argument.toString();
819             if (value.length() > MAX_ARG_LEN) {
820                 value = value.substring(0, MAX_ARG_LEN - 2) + "..";
821             }
822             if (argument instanceof String) {
823                 value = "\'" + value + "\'";
824             }
825         } catch (Exception e) {
826             value = shortName(argument);
827         }
828         return value;
829     }
830 
831     protected static String shortName(Object object) {
832         if (object == null || object.getClass() == null) return "unknownClass";
833         String name = getClassName(object);
834         if (name == null) return "unknownClassName"; // *very* defensive...
835         int lastDotPos = name.lastIndexOf('.');
836         if (lastDotPos < 0 || lastDotPos >= name.length() - 1) return name;
837         return name.substring(lastDotPos + 1);
838     }
839 
840     public static Class[] wrap(Class[] classes) {
841         Class[] wrappedArguments = new Class[classes.length];
842         for (int i = 0; i < wrappedArguments.length; i++) {
843             Class c = classes[i];
844             if (c == null) continue;
845             if (c.isPrimitive()) {
846                 if (c == Integer.TYPE) {
847                     c = Integer.class;
848                 } else if (c == Byte.TYPE) {
849                     c = Byte.class;
850                 } else if (c == Long.TYPE) {
851                     c = Long.class;
852                 } else if (c == Double.TYPE) {
853                     c = Double.class;
854                 } else if (c == Float.TYPE) {
855                     c = Float.class;
856                 }
857             } else if (isSuperclass(c, GString.class)) {
858                 c = String.class;
859             }
860             wrappedArguments[i] = c;
861         }
862         return wrappedArguments;
863     }
864 
865     public static boolean sameClasses(Class[] params, Object[] arguments, boolean weakNullCheck) {
866         if (params.length != arguments.length)
867             return false;
868 
869         for (int i = params.length - 1; i >= 0; i--) {
870             Object arg = arguments[i];
871             Class compareClass = getClassWithNullAndWrapper(arg);
872             if (params[i] != compareClass) return false;
873         }
874 
875         return true;
876     }
877 
878     private static Class getClassWithNullAndWrapper(Object arg) {
879         if (arg == null) return null;
880         if (arg instanceof Wrapper) {
881             Wrapper w = (Wrapper) arg;
882             return w.getType();
883         }
884         return arg.getClass();
885     }
886 
887     public static boolean sameClasses(Class[] params, Object[] arguments) {
888         if (params.length != arguments.length)
889             return false;
890 
891         for (int i = params.length - 1; i >= 0; i--) {
892             Object arg = arguments[i];
893             if (arg == null) {
894                 if (params[i] != null)
895                     return false;
896             } else {
897                 if (params[i] != getClassWithNullAndWrapper(arg))
898                     return false;
899             }
900         }
901 
902         return true;
903     }
904 
905     public static boolean sameClasses(Class[] params) {
906         if (params.length != 0)
907             return false;
908 
909         return true;
910     }
911 
912     public static boolean sameClasses(Class[] params, Object arg1) {
913         if (params.length != 1)
914             return false;
915 
916         if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
917 
918         return true;
919     }
920 
921     public static boolean sameClasses(Class[] params, Object arg1, Object arg2) {
922         if (params.length != 2)
923             return false;
924 
925         if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
926         if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
927 
928         return true;
929     }
930 
931     public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3) {
932         if (params.length != 3)
933             return false;
934 
935         if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
936         if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
937         if (params[2] != getClassWithNullAndWrapper(arg3)) return false;
938 
939         return true;
940     }
941 
942     public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3, Object arg4) {
943         if (params.length != 4)
944             return false;
945 
946         if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
947         if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
948         if (params[2] != getClassWithNullAndWrapper(arg3)) return false;
949         if (params[3] != getClassWithNullAndWrapper(arg4)) return false;
950         
951         return true;
952     }
953 
954     public static boolean sameClass(Class[] params, Object arg) {
955         return params[0] == getClassWithNullAndWrapper(arg);
956 
957     }
958 
959     public static Class[] castArgumentsToClassArray(Object[] argTypes) {
960         if (argTypes == null) return EMPTY_CLASS_ARRAY;
961         Class[] classes = new Class[argTypes.length];
962         for (int i = 0; i < argTypes.length; i++) {
963             Object argType = argTypes[i];
964             if (argType instanceof Class) {
965                 classes[i] = (Class) argType;
966             } else if (argType == null) {
967                 classes[i] = null;
968             } else {
969 //                throw new IllegalArgumentException("Arguments to method [respondsTo] must be of type java.lang.Class!");
970                 classes[i] = argType.getClass();
971             }
972         }
973         return classes;
974     }
975 
976     public static void unwrap(Object[] arguments) {
977         //
978         // Temp code to ignore wrapped parameters
979         // The New MOP will deal with these properly
980         //
981         for (int i = 0; i != arguments.length; i++) {
982             if (arguments[i] instanceof Wrapper) {
983                 arguments[i] = ((Wrapper) arguments[i]).unwrap();
984             }
985         }
986     }
987 
988     /**
989      * Sets the meta class for an object, by delegating to the appropriate
990      * {@link DefaultGroovyMethods} helper method. This method was introduced as
991      * a breaking change in 2.0 to solve rare cases of stack overflow. See GROOVY-5285.
992      *
993      * The method is named doSetMetaClass in order to prevent misusages. Do not use
994      * this method directly unless you know what you do.
995      *
996      * @param self the object for which to set the meta class
997      * @param mc the metaclass
998      */
999     public static void doSetMetaClass(Object self, MetaClass mc) {
1000         if (self instanceof GroovyObject) {
1001             DefaultGroovyMethods.setMetaClass((GroovyObject)self, mc);
1002         } else {
1003             DefaultGroovyMethods.setMetaClass(self, mc);
1004         }
1005     }
1006 
1007     /**
1008      * Converts a String into a standard property name.
1009      *
1010      * @param prop the original name
1011      * @return the converted name
1012      */
1013     public static String convertPropertyName(String prop) {
1014         if (Character.isDigit(prop.charAt(0))) {
1015             return prop;
1016         }
1017         return java.beans.Introspector.decapitalize(prop);
1018     }
1019 }